package cn.scs.component;

import cn.scs.common.FileInfo;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.io.OutputFormat;
import org.dom4j.io.SAXReader;
import org.dom4j.io.XMLWriter;


import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class MetaHandler {

    final static String metaRootLabel = "Root";    // 根节点 标签名称
    final static String metaFileLabel = "File";    // 文件节点 标签名称

    /*                      *\
         Init    Instance
    \*                      */

    /*                      *\
        Integrated  Method
    \*                      */
    // 获取 fileSystem
    public Map<String, FileInfo> getFileSystem(String path) {
        return openImage(path).fileSystem;
    }
    // 报存 fileSystem
    public boolean saveFileSystem(Map<String, FileInfo> fileSystem, String path) {
        InfoToElement res = saveImage(fileSystem.get("/"), path);
        return res!=null;
    }
    // 打开镜像
    public ElementToInfo openImage(String path) {
        FileInfo fileInfo = new FileInfo("SimpleDFS","",true,0,"System","System", 0);
        Map<String, FileInfo> fileSystem = new HashMap<>();
        ElementToInfo loader = null;
        try {
            Document dom = getXMLDom(path);
            Element meta = dom.getRootElement();
            loader = new ElementToInfo(meta, fileInfo, this.metaFileLabel, fileSystem);
            loader.run();   // 启动加载
        } catch (Exception e) {
            e.printStackTrace();
        }
        return loader;   // 获取 fileSystem
    }
    // 保存镜像
    public InfoToElement saveImage(FileInfo metaRoot, String path) {
        Element element = DocumentHelper.createElement("Storage");
        InfoToElement writer = new InfoToElement(metaRoot, element, this.metaRootLabel, this.metaFileLabel);
        writer.run();   // 启动写入
        Document dom = DocumentHelper.createDocument();
        dom.add(element.element(this.metaRootLabel));
        // save
        try {
            saveXML(dom, path);
            System.out.println("MetaImage updated to: " + path + "\t" + LocalDateTime.now());
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        }
        return writer;
    }

    /*              *\
        Operation
    \*              */
    // 拼接目录
    public static String pathStd(String path) {
        String std = "";
        for (char symbol:path.toCharArray()) {
            if (symbol == '/' && std.endsWith("/")) {

            } else {
                std += symbol;
            }
        }
        return std;
    }

    /*              *\
        Meta - XML
    \*              */
    // FileInfo 转 XML-Element
    public static class InfoToElement extends Thread {

        private FileInfo object;    // 待转换的FileInfo
        private Element parent;     // 若为子对象，需存入的列表
        private String nodeName;    // 储存的节点 标签名称
        private String childName;   // 子节点的 标签名称

        public InfoToElement(FileInfo fileInfo, Element parent, String nodeName, String childName) {
            this.object = fileInfo;
            this.parent = parent;
            this.nodeName = nodeName;
            this.childName = childName;
        }
        // 工具 追加数组至节点
        public <T> void addElementList(Element element, List<T> info, String node) {
            for (T item:info) {
                element.addElement(node).setText(String.valueOf(item));
            }
        }

        // 覆写 run 用于转换Element
        @Override
        public void run(){
            Element element = DocumentHelper.createElement(this.nodeName);  // 创建以 文件名称 命名的节点
            // 文件基本属性 存于标签属性中
            element.addAttribute("fileName", String.valueOf(this.object.getFileName()) );
            element.addAttribute("fileSize", String.valueOf(this.object.getFileSize()) );
            element.addAttribute("creationTime", String.valueOf(this.object.getCreationTime()) );
            element.addAttribute("isDirectory", String.valueOf(this.object.isDirectory()) );
            element.addAttribute("owner", this.object.getOwner() );
            element.addAttribute("group", this.object.getGroup() );
            // 文件附加信息 存于下级标签
            this.addElementList(element.addElement("Locations"), this.object.getLocations(), "locations");
            this.addElementList(element.addElement("Status"), this.object.getStatus(), "status");
            if (this.object.isDirectory()) {
                // 是目录则创建 children
                Element children = element.addElement("Children");
                // 遍历多线程递归添加子节点 [若为文件，则不启用多线程减少开销]
                for (FileInfo childInfo:this.object.getChildren()) {
                    if (childInfo.isDirectory()) {new InfoToElement(childInfo, children, this.childName, this.childName).run();}
                    else {new InfoToElement(childInfo, children, this.childName, this.childName).run();}
                }
            }
            parent.add(element);    // 添加至父节点
        }   // run End

    }
    // XML-Element 转 FileInfo
    public static class ElementToInfo extends Thread {

        private Element node;
        private FileInfo parent;
        private String childName;
        private Map<String, FileInfo> fileSystem;

        public ElementToInfo(Element element, FileInfo parent, String childName, Map<String, FileInfo> fileSystem){
            this.node = element;
            this.parent = parent;
            this.childName = childName;
            this.fileSystem = fileSystem;
        }

        // 工具 elements 转 List
        public <T> List<T> getElementList(Element element, String node, Class type) {
            List<T> queue = new ArrayList<>();
            List<Element> elements = element.elements(node);
            for (Element e:elements) {
                try {
                    Constructor<T> constructor = type.getConstructor(String.class);
                    constructor.setAccessible(true);
                    T val = constructor.newInstance(e.getText());
                    queue.add( val );
                } catch (Exception err) {
                    err.printStackTrace();
                }
            }
            return queue;
        }

        @Override
        public void run(){
            String path = MetaHandler.pathStd(this.parent.getPath() + "/" + this.node.attributeValue("fileName"));    // 获取Path 映射FileSystem
            FileInfo fileInfo = new FileInfo(this.node.attributeValue("fileName"), path,
                    Boolean.parseBoolean(this.node.attributeValue("isDirectory")),
                    Long.parseLong(this.node.attributeValue("fileSize")),
                    this.node.attributeValue("owner"),
                    this.node.attributeValue("group"),
                    Long.parseLong(this.node.attributeValue("creationTime")) );
            fileInfo.setParent(this.parent);
            this.parent.getChildren().add(fileInfo);
            fileInfo.setLocations( getElementList(this.node.element("Locations"), "locations", String.class) );
            fileInfo.setStatus( getElementList(this.node.element("Status"), "status", Integer.class) );
            // 将fileInfo 映射至fileSystem
            this.fileSystem.put(path, fileInfo);
            // 增添子节点
            if ( fileInfo.isDirectory() && (this.node.element("Children") != null) ) {
                List<Element> children = this.node.element("Children").elements(this.childName);
                for (Element e:children) {
                    if (Boolean.parseBoolean(e.attributeValue("isDirectory"))) {
                        new ElementToInfo(e, fileInfo, this.childName, this.fileSystem).run();
                    } else {
                        new ElementToInfo(e, fileInfo, this.childName, this.fileSystem).run();
                    }
                }
            }
        } // run End
        // 获取FileSystem
        public Map<String, FileInfo> getFileSystem() {
            return this.fileSystem;
        }
    }

    // 设置根节点

    /*              *\
         XML - IO
    \*              */
    // 创建 新XML
    public static void saveXML(String rootName, String path) throws IOException {
        // 添加 根节点 初始化
        Document document = DocumentHelper.createDocument();
        document.addElement(rootName);
        saveXML(document, path);
    }
    // 保存 Document 至 XML
    public static void saveXML(Document dom, String path) throws IOException {
        //设置 缩进输出 与 编码格式
        OutputFormat format = OutputFormat.createPrettyPrint();
        format.setEncoding("UTF-8");
        XMLWriter writer = new XMLWriter(new FileWriter(path), format);
        //写入数据
        writer.write(dom); writer.close();
    }

    // 获取 XML Document
    public static Document getXMLDom(String path) throws DocumentException {
        SAXReader reader = new SAXReader();
        return reader.read(new File(path));
    }
}
